#include "directory_scanner.h"

#include <stdio.h>
#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#include <utils/error.h>
#include <sys/stat.h>
#include <linux/limits.h>
#include "subdirectories_queue.h"

struct top_directory
{
  DIR *top_dir_stream;
  char *top_path;
};

static struct top_directory top_directory = { 0 };

static bool
is_desired_extension(const char *node_name, const char *filter_extension)
{
  const char *extension = strrchr(node_name, '.');
  if (!extension) return false;
  if (strcmp(extension, filter_extension) == 0)
    return true;

  return false;
}

static bool
skip_dots(const char *node_name)
{
  if (strcmp(node_name, ".") == 0 || strcmp(node_name, "..") == 0)
    return true;

  return false;
}

static bool
next_file_in_directory(struct dirent *directory_entry,
                       const char *filter_extension)
{
  struct dirent *dir_entry;
  while ((dir_entry = readdir(top_directory.top_dir_stream)))
    {
      if (skip_dots(dir_entry->d_name)) continue;

      struct stat buffer;
      char path[PATH_MAX];
      snprintf(path, sizeof(path), "%s/%s", top_directory.top_path,
               dir_entry->d_name);

      if (stat(path, &buffer) == -1)
        PRINT_SYSTEM_ERROR_EXIT("Couldn't stat file");

      if (S_ISDIR(buffer.st_mode))
        {
          add_subdirectory(path, strlen(path) + 1);
          continue;
        }
      if (!is_desired_extension(dir_entry->d_name, filter_extension)) continue;
      if (S_ISREG(buffer.st_mode))
        {
          *directory_entry = *dir_entry;
          return true;
        }
      continue;
    }

  return false;
}

static inline void
try_initialize_top_directory(void)
{
  if (!top_directory.top_dir_stream)
    {
      if ((top_directory.top_dir_stream = opendir(top_directory.top_path)) == NULL)
        {
          PRINT_SYSTEM_ERROR_EXIT("Couldn't open the directory");
        }
    }
}

static bool
next_file(file_handler_ptr fileinfo)
{
  static bool is_already_closed;

  if (is_already_closed) return false;

  while (true)
    {
      try_initialize_top_directory();

      struct dirent directory_entry;
      if (next_file_in_directory(&directory_entry, get_filter_extension(fileinfo)))
        {
          initialize_file_path(fileinfo, top_directory.top_path,
                               directory_entry.d_name);
          return true;
        }
      else
        {
          (void) closedir(top_directory.top_dir_stream);
          free(top_directory.top_path);
          if (more_subdirectories())
            {
              top_directory.top_path = take_subdirectory();
              top_directory.top_dir_stream = NULL;
            }
          else
            {
              is_already_closed = true;
              initialize_subdirectories_queue();
              break;
            }
        }
    }

  return false;
}

gimme_file_func
scan_directory(const char *dir_path)
{
  {
    top_directory.top_path = malloc(strlen(dir_path) + 1);
    strcpy(top_directory.top_path, dir_path);
  }
  initialize_subdirectories_queue();

  return next_file;
}
